import java.io.BufferedReader;
import java.io.IOException;
import java.sql.Time;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;


public class AnalizadorLexico {
	
	
	private final int SIZE = 40;	//ver cual es el valor adecuado?
	final int CHAR_SIZE = 130;		//recalcular si se agregan caracteres imprimibles
	private boolean isFinal[];
	private String tokenID[];
	private String error[]; //tipo del estado
	private HashMap<Integer,Integer> M;
	private int cant;
	private HashSet<String> keywords;
	private HashSet<String> forbidden;
	private HashSet<Character> punctuation;
	private HashSet<Character> whitespace;
	private HashSet<Character> printableChars;
	private HashSet<Character> alphabet;
	private HashSet<Character> alphabet_eof;
	private HashSet<Character> letras_guion; //se podria sacar
	private HashSet<Character> letras_guion_num; //se podria sacar
	private HashSet<Character> sep; //separadores
	private int e;
	private int line;
	private BufferedReader buffer;
	private int current;
	private static final char EOF = 65535;
	
	public AnalizadorLexico(BufferedReader b) {
	/*	int a;
		try {
			while((a = b.read()) != -1)
				System.out.println("char "+a);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/
		init(b);
	}
	
	private void init(BufferedReader b) {
		buffer = b;
		e = 0;		
		line = 1;
		
		M = new HashMap<Integer,Integer>();
		cant = 0;
		isFinal = new boolean[SIZE];		
		tokenID = new String[SIZE];
		error = new String[SIZE];
		
		initWhiteSpace();
		initPrintableChars();
		initLetrasGuionNum();
		initAlphabet();
		initKeywords();
		initForbidden();
		
		
		initGeneral();
		initPunctuation();
		initSep();
		initWhiteSpaces();
		
		initComments();
		
		initIntLiteral();
		initCharLiteral();
		
		
		initOperator();
		initIdentifier();
		
		
		initStringLiteral();
	}
	
	private void hashInsert(int estado,int ch,int destino) {
	//	if (ch == EOF) ch = 0;
		M.put(estado * CHAR_SIZE + ch,destino);
	}
	
	private int hashGet(int estado,int ch) /*throws LexicalException*/ {
	//	System.out.println("hashGet e:"+e+" ch:"+ch);
	//	System.out.println("hashGet RESULTADO: "+M.get(estado * CHAR_SIZE + ch));
		//if ()
		if ((ch>=CHAR_SIZE && ch!=EOF) || M.get(estado * CHAR_SIZE + ch) == null)
			//throw new Exception("ERROR: caracter invalido - LINEA: "+line);
			error("ERROR: caracter invalido: "+ (char)ch + " - LINEA: "+line);
		//if (ch == EOF) ch = 0;
		return M.get(estado * CHAR_SIZE + ch);
	}
	
	private void initGeneral() {
		cant++;
		for(char c:alphabet)
			hashInsert(0,c,cant);
		error[cant] = "caracter inesperado";
	}
	
	private void initWhiteSpace() {
		Character k[] = {' ','\t','\n'};
		whitespace = new HashSet<Character>(Arrays.asList(k));
	}
	
	private void initComments() {
		//System.out.println("initcomment cant "+cant);
		cant++;
		hashInsert(0,'/',cant); //it is what there is
		
		isFinal[cant] = true;
		tokenID[cant] = "operator";
		
		for(char c:alphabet_eof) {
			hashInsert(cant,c,0);
			hashInsert(cant+1,c,cant+1);
			hashInsert(cant+2,c,cant+2);
			hashInsert(cant+3,c,cant+2);
		}
		
		hashInsert(cant,'/',cant+1);
		hashInsert(cant,'*',cant+2);
		
		hashInsert(cant+1,'\n',0);
		hashInsert(cant+1,EOF,0);
		hashInsert(cant+2,'*',cant+3);
		hashInsert(cant+2,EOF,cant+4);
		hashInsert(cant+3,'/',0);
		hashInsert(cant+3,EOF,cant+4);	
		
		cant+=4;		
		
		error[cant] = "Final de comentario inesperado";
		
	}	
	
	private void initPrintableChars() {
		printableChars = new HashSet<Character>();
		for(char c = 33; c <= 126 ; c++) {
			printableChars.add(c);		
		}
		printableChars.add(' ');
	}
	
	private void initSep() {
		Character k[] = {'+','-','*','/','%','>','<','=','!','&','|'};
		sep = new HashSet<Character>(Arrays.asList(k));
		for(char c:punctuation) sep.add(c);
		for(char c:whitespace) sep.add(c);
		sep.add(EOF);
	}
	
	private void initLetrasGuionNum() {
		letras_guion = new HashSet<Character>();
		for(char c = 'a' ; c<= 'z' ; c++) {
			letras_guion.add(c);
			letras_guion.add((char)(c+'A'-'a'));
		}
		letras_guion.add('_');
		
		letras_guion_num = new HashSet<Character>(letras_guion);
		for(char c= '0';c<='9'; c++) 
			letras_guion_num.add(c);
	}
	
	private void initAlphabet() {
		alphabet = new HashSet<Character>(printableChars);
		alphabet.add('\t');
		alphabet.add('\n');
		
		alphabet_eof = (HashSet<Character>)alphabet.clone();
		alphabet_eof.add(EOF);
		//for(char c:alphabet_eof) System.out.println("alpha "+c);
	}
	
	private void initKeywords() {
		String k[] = {"if","return","class","new","super","else","this","extends",
				"while","for","void","String","classDef","boolean","char","int"};
		keywords = new HashSet<String>(Arrays.asList(k));
	}
	
	private void initForbidden() {
		String k[] = {"abstract","default","long","switch","try","byte","do",
				"implements","native","synchronized","volatile","case","double","import",
				"goto","throw","catch","final","instanceof","package","throws","const",
				"finally","interface","short","transient","break","continue","private",
				"protected","public","static","float","byvalue","cast","future",
				"generic","inner","none","operator","outer","rest","var"};		
		forbidden = new HashSet<String>(Arrays.asList(k));
	}
	
	private void initIntLiteral() {
		cant++;
		
		for(char c:alphabet) 
			hashInsert(cant,c,cant+1);
		
		for(char c = '0'; c<='9'; c++) { //se puede poner mas lindo
			hashInsert(0,c,cant);
			hashInsert(cant,c,cant);
		}	
		for(char c:sep)
			hashInsert(cant,c,0);
		isFinal[cant] = true;
		tokenID[cant] = "intLiteral";
		error[++cant] = "literal numérico mal formado";
	}
	
	private void initCharLiteral() {
		cant++;
		hashInsert(0,'\'',cant);
		
		for(char c:alphabet_eof) 
			for(int i = 0; i<4 ; i++)
				hashInsert(cant+i,c,cant+4);
	    
		for(char c:printableChars)
			if (c != '\'') hashInsert(cant,c,cant+2);
		
		hashInsert(cant,'\\',++cant);
		
		for(char c:printableChars) hashInsert(cant,c,cant+1);
				
		hashInsert(++cant,'\'',++cant);
		
		for(char c:sep)
			hashInsert(cant,c,0);
		
		isFinal[cant] = true;
		tokenID[cant] = "charLiteral";
		error[++cant] = "char literal mal formado";
	}
		
	private void initStringLiteral() {
		cant++;
		
		for(char c:alphabet_eof) {
			hashInsert(cant,c,cant+2);
			hashInsert(cant+1,c,cant+2);
		}
		
		hashInsert(0,'"',cant);
		
		for(char c:printableChars)
			hashInsert(cant,c,cant);
		
		hashInsert(cant,'"',++cant);
		
		for(char c:sep)
			hashInsert(cant,c,0);
		
		isFinal[cant] = true;
		tokenID[cant] = "stringLiteral";
		error[++cant] = "string mal formado";
	}
	
	private void initPunctuation() {
		Character p[] = {'(',')','{','}',';',',','.'};
		cant++;
		for(int i =0 ; i<p.length ; i++)
			hashInsert(0,p[i],cant);		
		
		for(char c:alphabet_eof)
			hashInsert(cant,c,0);		
		
		isFinal[cant] = true;
		tokenID[cant] = "punctuation";
		
		punctuation = new HashSet<Character>(Arrays.asList(p));
	}

	private void initOperator() {
		
		for(char c:alphabet_eof) {
			hashInsert(cant+6,c,cant+11);
			hashInsert(cant+8,c,cant+11);
		}
		
		hashInsert(0,'=',cant+1);
		hashInsert(0,'>',cant+2);
		hashInsert(0,'<',cant+3);
		hashInsert(0,'!',cant+4);
		hashInsert(0,'&',cant+6);
		hashInsert(0,'|',cant+8);
		hashInsert(0,'+',cant+10);
		hashInsert(0,'-',cant+10);
		hashInsert(0,'*',cant+10);
		hashInsert(0,'%',cant+10);
		
		for(int i = 1 ; i <=10 ; i++) 
			if (i!=6 && i!=8) {
				isFinal[cant+i] = true;
				tokenID[cant+i] = "operator";
				for(char c:alphabet_eof)
					hashInsert(cant+i,c,0);
			}	
		
		hashInsert(cant+1,'=',cant+5);
		hashInsert(cant+2,'=',cant+5);
		hashInsert(cant+3,'=',cant+5);
		hashInsert(cant+4,'=',cant+5);
		hashInsert(cant+6,'&',cant+7);
		hashInsert(cant+8,'|',cant+9);
		
		cant += 10;
		error[++cant] = "operador mal formado";
	}
	
	private void initIdentifier() {
		cant++;		
		
		for(char c:alphabet)
			hashInsert(cant,c,cant+1);
			
		for (char c:letras_guion) 
			hashInsert(0,c,cant);			
			
		for(char c:letras_guion_num) 
			hashInsert(cant,c,cant);
						
		for(char c:sep) 
			hashInsert(cant,c,0);
		
		isFinal[cant] = true;
		tokenID[cant] = "identifier";
		
		error[++cant] = "identificador mal formado";
	}
	
	private void initWhiteSpaces() {
		for(char c:whitespace)
			hashInsert(0,c,0);		
		//EOF
		hashInsert(0,EOF,0);
	}
	
	public Token getNextToken() {
		StringBuilder lexema = new StringBuilder();
				
		while (true) { 
			if (current == -1) return null;
			try {
				buffer.mark(5);
				current = buffer.read();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}			
			char c = (char)current;
		//System.out.println("e "+e+" c "+c+" "+(int)c+" line "+line);
			
			/*if (c==65535) {//EOF
				if (e!=0 && !isFinal[e]) throw new Exception("ERROR: token incompleto o comentario incorrectamente cerrado");
				if (e == 0) return null;
				return dump(e,lexema);
			}*/
			
			//PARCHE
			//System.out.println("e "+e+" current "+current);
			//if (e == 0 && current==-1) return null;
				
			int old = e;
			e = hashGet(e,c);
			if (error[e]!=null) error("ERROR: "+error[e]+" - LINEA: "+line);
			if (c == '\n') line++;
			if (e == 0 && isFinal[old]) {
				if (c == '\n') line--; //patch
				return dump(old,lexema);				
			}	
			
			lexema.append(c);
			if (e==0) lexema = new StringBuilder();
		}
	}
	
	private Token dump(int old,StringBuilder lexema){
		Token tok;
		String res = lexema.toString();
		
		if (tokenID[old].equals("identifier")) {
			if (forbidden.contains(res))
				throw new LexicalException("ERROR: palabra prohibida: "+res + " - LINEA: "+line); //AGREGAR NUMERO DE LINEA
			if (keywords.contains(res))
				tok = new Token(res,res,line);
			else if (res.equals("true") || res.equals("false"))
				//tok = new Token("boolLiteral",res,line);
				tok = new Token(res,res,line);
			else if (res.equals("null"))
				tok = new Token("null",res,line);
			else tok = new Token("identifier",res,line);
			
		}
		else if (tokenID[old].equals("operator") || tokenID[old].equals("punctuation")) {
			tok = new Token(res,res,line);
		}
		else {
			tok = new Token(tokenID[old],res,line);
		}
		try {
			buffer.reset();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		lexema = new StringBuilder();
		return tok;
	}	
	
	private void error(String s)/* throws LexicalException*/ {
		throw new LexicalException(s);
	}
}

